/**
 * \file WaveFile.h
 * \brief Module handling WaveFile object which represents WAVE file.
 * 
 * Notes : Doesn't manage endianness.
 *         Assumes a char is one byte long, a short at least 2 bytes long and an
 *         int at least 4 bytes long.
 *         
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "errors.h"
#include "WaveFile.h"

/**
 * \brief Buffer size for charArrayToStr.
 */ 
#define CATS_BUFFER_SIZE 10

/**
 * Converts a 2 unsigned char long representation of a short into to it's numeric value.
 * @param cnbr Unsigned char array to convert.
 * @return result of conversion.
 */ 
 unsigned short uchar2ToUshort(const unsigned char cnbr[2]) {
	return cnbr[0] + 256 * cnbr[1]; 
}

/**
 * Converts a 2 char long representation of a short into to it's numeric value.
 * @param cnbr Char array to convert.
 * @return result of conversion.
 */ 
short uchar2ToShort(const unsigned char cnbr[2]) {
	return cnbr[0] + 256 * cnbr[1]; 
}

/**
 * Converts a 3 unsigned char long representation of an int to it's numeric
 * value.
 * @param cnbr Unsigned char array to convert.
 * @return result of conversion.
 */ 
/*static unsigned int uchar3ToInt(const unsigned char cnbr[3]) {
	return cnbr[0] + 256 * cnbr[1] + 65536 * cnbr[2]; 
}*/

/**
 * Converts a 4 unsigned char long representation of an int to it's numeric
 * value.
 * @param cnbr Unsigned char array to convert.
 * @return result of conversion.
 */ 
static unsigned int uchar4ToInt(const unsigned char cnbr[4]) {
	return cnbr[0] + 256 * cnbr[1] + 65536 * cnbr[2] + 16777216 * cnbr[3]; 
}

/**
 * Reads the header of the wav file.
 * @param waveFile WaveFile to read.
 * @return Error code.
 */
static void readHeader(WaveFile * waveFile) {
	/* Block which declares a WAVE FILE. */
	fread(waveFile->fileTypeBlocID, sizeof(char), 4, waveFile->file);
	fread(waveFile->fileSize, sizeof(char), 4, waveFile->file);
	waveFile->fs = uchar4ToInt(waveFile->fileSize);
	fread(waveFile->fileFormatID, sizeof(char), 4, waveFile->file);
	fread(waveFile->formatBlocID, sizeof(char), 4, waveFile->file);
	fread(waveFile->blockSize, sizeof(char), 4, waveFile->file);

	/* Block describing audio format. */
	fread(waveFile->audioFormat, sizeof(char), 2, waveFile->file);
	fread(waveFile->nbrCanaux, sizeof(char), 2, waveFile->file);
	fread(waveFile->frequence, sizeof(char), 4, waveFile->file);
	waveFile->freq = uchar4ToInt(waveFile->frequence);
	fread(waveFile->bytePerSec, sizeof(char), 4, waveFile->file);
	fread(waveFile->bytePerBloc, sizeof(char), 2, waveFile->file);
	fread(waveFile->bitsPerSample, sizeof(char), 2, waveFile->file);
	waveFile->bytesPerSample = uchar2ToUshort(waveFile->bitsPerSample) / 8;

	/* Data block. */
	fread(waveFile->dataBlocID, sizeof(char), 4, waveFile->file);
	fread(waveFile->dataSize, sizeof(char), 4, waveFile->file);
}

/**
 * Return a string from an array of char which doesn't necessary contains a '\0'
 * terminating character.
 * @param ar Array of char.
 * @param arl Length of the array.
 * @param Pointer on the result of the conversion.
 */
static char *charArrayToStr(const char ar[], int arl) {
	static char ret[CATS_BUFFER_SIZE] = "";
	
	assert(arl < CATS_BUFFER_SIZE);
	assert(ar);
	
	ret[arl] = '\0';
	while (arl--) {
		ret[arl] = ar[arl];
	}
	return ret;
}

/**
 * Prints a textual representation of the WAVE file header to the given file.
 * @param waveFile WaveFil of wich the header will be written.
 * @param file File in wich the output will be written.
 */
static void headerToFile(void *waveFile, FILE *file) {
	WaveFile *wav = (WaveFile *)waveFile;
	fprintf(file, "#Header content : \n");
	fprintf(file, "#  fileTypeBlocID : \"%s\"\n", charArrayToStr(wav->fileTypeBlocID, 4));
	fprintf(file, "#  fileSize :       %d bytes\n", wav->fs + 8);
	fprintf(file, "#  fileFormatID :   \"%s\"\n", charArrayToStr(wav->fileFormatID, 4));
	fprintf(file, "#  formatBlocID :   \"%s\"\n", charArrayToStr(wav->formatBlocID, 4));
	/* Block size : size in bytes of the header section after block size and
	 * before data section */
	fprintf(file, "#  blockSize :      %d bytes\n\n", uchar4ToInt(wav->blockSize));
	
	fprintf(file, "#  audioFormat :    %d\n", uchar2ToUshort(wav->audioFormat));
	fprintf(file, "#  nbrCanaux :      %d\n", uchar2ToUshort(wav->nbrCanaux));
	fprintf(file, "#  frequence :      %d Hz\n", uchar4ToInt(wav->frequence));
	fprintf(file, "#  bytePerSec :     %d Bps\n", uchar4ToInt(wav->bytePerSec));
	fprintf(file, "#  bytePerBloc :    %d\n", uchar2ToUshort(wav->bytePerBloc));
	fprintf(file, "#  bitsPerSample :  %d\n", uchar2ToUshort(wav->bitsPerSample));
	fprintf(file, "#    (bytesPerSample : %d)\n\n", wav->bytesPerSample);
	
	fprintf(file, "#  dataBlocID :     %s\n", charArrayToStr(wav->dataBlocID, 4));
	fprintf(file, "#  dataSize :       %d bytes\n", uchar4ToInt(wav->dataSize));
}

ErrCode wopen(void *waveFile, const char fileName[]) {
	WaveFile *wav = (WaveFile *)waveFile;
	if (fileName == NULL) return NULL_POINTER;
	
	/* If a char size is wrong, nothing will work, better dying... So after this
	 * point, a char is assumed to be une byte long.
	 */
	if (sizeof(char) != 1) {
		fprintf(stderr, "La taille d'un char ne fait pas un octet.\n");
		exit(EXIT_FAILURE);
	}

	/* Open file. */
	wav->file = fopen(fileName, "r");
	if (wav->file == NULL) return CANT_OPEN_FILE;
	
	/* TODO vérifier que le fichier fait bien au moins 44 octets */
	
	/* Copy file name. */
	wav->fileName = malloc(strlen(fileName) + 1);
	if (wav->fileName == NULL) return MEMORY_ALLOC;
	sprintf(wav->fileName, "%s", fileName);
	
	readHeader(wav);
	
	/* Verify header */
	/* TODO 
	 * Refuse multi channel Wave files */
	
	/* Allocate buffer. */
	wav->bufferSize = BUFFER_DURATION * wav->freq * wav->bytesPerSample / 1000;
	wav->buffer = calloc(wav->bufferSize, 1);
	if (wav->buffer == NULL) return MEMORY_ALLOC;
	
	/* Read first segment */
	fread(wav->buffer, wav->bufferSize, 1, wav->file);
	if (wav->buffer == NULL) {
		fprintf(stderr, "Première lecture foirée.\n");
	}

	return SUCCESS;
}

/* TODO accepter un fichier non correctement ouvert. */
void wclose(void *waveFile) {
	WaveFile *wav = (WaveFile *)waveFile;
	free(wav->fileName);
	fclose(wav->file);
}

void toStdout(void *waveFile) {
	headerToFile(waveFile, stdout);
}

void *getNextSection(void *waveFile) {
	WaveFile *wav = (WaveFile *)waveFile;
	fread(wav->buffer, wav->bufferSize, 1, wav->file);
	return wav->buffer;
}
